using System;
using System.Linq;
using NUnit.Framework;
using TLDRML.Behaviors;
using TLDRML.Nodes;
using TLDRML.Tokens;
using Should;
using System.Collections.Generic;

namespace TLDRMLTests
{
    public class NodeContext
    {
        public T ParseTest<T>(params Token[] tokens) where T : Node, new()
        {
            var node = new T();
            node.Parse(tokens);
            return node;
        }
    }

    [TestFixture]
    public class NodeTests : NodeContext
    {
        [Test]
        public void ValueNode_LiteralToken_ValueMatches()
        {
            ParseTest<ValueNode>(
                    new LiteralToken(null, "aa")
                ).Value.ShouldEqual("aa");
        }

        [Test]
        public void ValueNode_StringToken_ValueMatches()
        {
            ParseTest<ValueNode>(
                    new StringToken(null, "aa")
                ).Value.ShouldEqual("aa");
        }

        [Test]
        [ExpectedException(typeof(InvalidOperationException))]
        public void ValueNode_InvalidToken_ThrowsInvalidOperation()
        {
            ParseTest<ValueNode>(new IndentToken(null));
        }

        [Test]
        public void Parse_TwoTokensIn_OneTokenConsumedOneRemains()
        {
            var expected = new LiteralToken(null, "bb");

            var node = new ValueNode();
            var results = node.Parse(new[] {new LiteralToken(null, "aa"), expected});

            results.ShouldContain(expected);
        }

        [Test]
        public void AttributeNode_LiteralEqualsValue_NameMatches()
        {
            ParseTest<AttributeNode>(
                new LiteralToken(null, "aa"),
                new EqualsToken(null),
                new LiteralToken(null, "bb")
                ).Name.ShouldEqual("aa");
        }

        [Test]
        public void AttributeNode_LiteralEqualsValue_ValueMatches()
        {
            ParseTest<AttributeNode>(
                new LiteralToken(null, "aa"),
                new EqualsToken(null),
                new StringToken(null, "bb")
                ).Value.ShouldEqual("bb");
        }

        [Test]
        public void AttributeNode_NameDeclarationLiteralWithValue_NameIsName()
        {
            ParseTest<AttributeNode>(
                new NameDeclarationToken(null),
                new StringToken(null, "aa")
                ).Name.ShouldEqual("Name");
        }

        [Test]
        public void AttributeNode_NameDeclarationBehaviorChangedToId_NameIsId()
        {
            NameDeclarationBehavior.DefaultName = () => "Id";

            ParseTest<AttributeNode>(
                new NameDeclarationToken(null),
                new StringToken(null, "aa")
                ).Name.ShouldEqual("Id");
        }

        [Test]
        public void AttributeNode_NameDeclarationLiteralWithValue_ValueMatches()
        {
            ParseTest<AttributeNode>(
                new NameDeclarationToken(null),
                new StringToken(null, "aa")
                ).Value.ShouldEqual("aa");
        }

        [Test]
        public void ElementNode_LiteralPlusNewLine_TypeMatches()
        {
            ParseTest<ElementNode>(
                new LiteralToken(null, "aa"),
                new NewLineToken(null)
                ).Type.ShouldEqual("aa");
        }

        [Test]
        public void ElementNode_LiteralPlusAttributePlusNewLine_AttributeAddedToElement()
        {
            AttributeNode attr =
                ParseTest<ElementNode>(
                    new LiteralToken(null, "aa"),
                    new LiteralToken(null, "bb"),
                    new EqualsToken(null),
                    new LiteralToken(null, "cc"),
                    new NewLineToken(null)
                    ).AttributeNodes.Single();

            attr.Name.ShouldEqual("bb");
            attr.Value.ShouldEqual("cc");
        }

        [Test]
        public void ElementNode_LiteralPlusTwoAttributesPlusNewLine_ElementHasTwoAttributes()
        {
            ParseTest<ElementNode>(
                new LiteralToken(null, "aa"),
                new LiteralToken(null, "bb"),
                new EqualsToken(null),
                new LiteralToken(null, "cc"),
                new LiteralToken(null, "dd"),
                new EqualsToken(null),
                new LiteralToken(null, "ee"),
                new NewLineToken(null)
                ).AttributeNodes.Count().ShouldEqual(2);
        }

        [Test]
        public void ElementNode_IndentedElement_AddedAsChildNode()
        {
            var element = ParseTest<ElementNode>(
                new LiteralToken(null, "aa"),
                new NewLineToken(null),
                new IndentToken(null),
                new LiteralToken(null, "bb"),
                new NewLineToken(null),
                new OutdentToken(null)
                );

            var child = (ElementNode)element.ChildNodes.Single();

            element.Type.ShouldEqual("aa");
            child.Type.ShouldEqual("bb");
        }

        [Test]
        public void AttributesNode_Attribute_AddedToChildren()
        {
            var attrNode = ParseTest<AttributeNode>(
                new LiteralToken(null, "aa"),
                new EqualsToken(null),
                new LiteralToken(null, "bb")
                );

            attrNode.Name.ShouldEqual("aa");
            attrNode.Value.ShouldEqual("bb");
        }

        [Test]
        public void AttributesNode_AttributeIsConsumed_NewLineTokenRemains()
        {
            var tokens = new List<Token>
                         {
                             new LiteralToken(null, "aa"),
                             new EqualsToken(null),
                             new LiteralToken(null, "bb"),
                             new NewLineToken(null)
                         };

            var remainder = new AttributeNode().Parse(tokens).Single();

            remainder.ShouldEqual(tokens[3]);
        }

        [Test]
        public void ElementNode_LiteralWithTwoAttributes_AttributesAddedToChildren()
        {
            var elementNode =
                ParseTest<ElementNode>(
                    new LiteralToken(null, "aa"),
                    new LiteralToken(null, "bb"),
                    new EqualsToken(null),
                    new LiteralToken(null, "cc"),
                    new LiteralToken(null, "dd"),
                    new EqualsToken(null),
                    new StringToken(null, "ee"),
                    new NewLineToken(null)
                    );

            AttributeNode first = elementNode.AttributeNodes.ElementAt(0);
            AttributeNode second = elementNode.AttributeNodes.ElementAt(1);

            first.Name.ShouldEqual("bb");
            first.Value.ShouldEqual("cc");
            second.Name.ShouldEqual("dd");
            second.Value.ShouldEqual("ee");
        }

        [Test]
        public void ElementDeclarationNode_IndentedWithTwoLiterals_RecursivelyAddsTwoChildren()
        {
            var element =
                ParseTest<ElementNode>(
                    new LiteralToken(null, "aa"),
                    new NewLineToken(null),
                    new IndentToken(null),
                    new LiteralToken(null, "bb"),
                    new NewLineToken(null),
                    new LiteralToken(null, "cc"),
                    new NewLineToken(null),
                    new OutdentToken(null)
                    );

            element.ChildNodes.Count.ShouldEqual(2);
        }

        [Test]
        public void DocNode_SingleLiteral_AddsSingleElementDecl()
        {
            var docNode =
                ParseTest<DocNode>(
                    new LiteralToken(null, "aa"),
                    new NewLineToken(null)
                    );

            docNode.ChildNodes.Count.ShouldEqual(1);
        }

        [Test]
        public void DocNode_LiteralWithChildLiteral_OneChildInDoc()
        {
            var docNode =
                ParseTest<DocNode>(
                    new LiteralToken(null, "aa"),
                    new NewLineToken(null),
                    new IndentToken(null),
                    new LiteralToken(null, "bb"),
                    new NewLineToken(null),
                    new OutdentToken(null)
                    );

            docNode.ChildNodes.Count.ShouldEqual(1);
        }
    }
}